using System.Collections.Generic;
using System.Linq;
using Managistics.Core.Infrastructure;
using Managistics.Core.Repositories;
using Managistics.Core.Services;
using Managistics.Model;
using Managistics.Model.ValueObject;
using System;
using Managistics.Utility.Common;

namespace Managistics.Core.Services.Impl
{
    public class PurchaseOrderService : IPurchaseOrderService
    {
        private readonly IInputRepository _inputRepository;
        private readonly IInputSheetDetailRepository _inputSheetDetailRepository;
        private readonly IPurchaseOrderRepository _purchaseOrderRepository;
        private readonly IPurchaseOrderDetailRepository _purchaseOrderDetailRepository;

        /// <summary>
        /// PurchaseOrderService constructor
        /// </summary>
        /// <param name="inputRepository"></param>
        /// <param name="purchaseOrderRepository"></param>
        /// <param name="purchaseOrderDetailRepository"></param>
        /// <param name="inputSheetDetailRepository"></param>
        public PurchaseOrderService(IInputRepository inputRepository, IPurchaseOrderRepository purchaseOrderRepository,
            IPurchaseOrderDetailRepository purchaseOrderDetailRepository,
            IInputSheetDetailRepository inputSheetDetailRepository)
        {
            _inputRepository = inputRepository;
            this._purchaseOrderRepository = purchaseOrderRepository;
            this._purchaseOrderDetailRepository = purchaseOrderDetailRepository;
            this._inputSheetDetailRepository = inputSheetDetailRepository;
        }
        
        /// <summary>
        /// Load PurchaseOrders
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        [UnitOfWork]
        public List<PurchaseOrder> GetPurchaseOrders(PurchaseOrderParam p)
        {
            return this._purchaseOrderRepository.GetPurchaseOrders(p);
        }

        /// <summary>
        /// load PurchaseOrderDetails
        /// </summary>
        /// <param name="vo"></param>
        /// <returns></returns>
        [UnitOfWork]
        public List<PurchaseOrderDetail> GetPurchaseOrderDetails(string orderId)
        {
            return this._purchaseOrderDetailRepository.GetPurchaseOrderDetailByOrderId(orderId);
        }

        /// <summary>
        /// Load InputSheets by orderId
        /// </summary>
        /// <param name="Orderid"></param>
        /// <returns></returns>
        [UnitOfWork]
        public List<InputSheet> GetInputSheetsByOrder(string Orderid)
        {
            return this._inputRepository.GetInputSheetsOfOrderId(Orderid);
        }
        
        /// <summary>
        /// Get list of InputSheetDetails base on orderId and inputSheetId
        /// </summary>
        /// <param name="orderId"></param>
        /// <param name="inputSheetId"></param>
        /// <returns></returns>
        [UnitOfWork]
        public List<InputSheetDetailRecord> GetInputSheetDetails(string orderId, string inputSheetId)
        {
            
            //1) get all ordered quantity of order base on order id if of input sheet (no need to group by item)
            List<PurchaseOrderDetail> orderedList = this._purchaseOrderDetailRepository.GetPurchaseOrderDetailByOrderId(orderId);

            //2) get all inputed quantity base on order id (group by item, sum(inputed quantity)) by linq
            List<InputedQuantityRecord> inputedList = (from d in orderedList
                                                        group d by d.ItemId into GroupOrderedDetail
                                                        select new InputedQuantityRecord
                                                        {
                                                            ItemId = GroupOrderedDetail.Select(g => g.ItemId).First(),
                                                            Quantity = GroupOrderedDetail.Sum(g => g.Quantity),
                                                        }).ToList<InputedQuantityRecord>();

            //3) update existing value of detail list of input sheet with:
            Func<List<PurchaseOrderDetail>, List<InputedQuantityRecord>, string, float?> GetExisting = (ordered, inputed, itemId)
                => ordered.Single(o => o.ItemId == itemId).Quantity - inputed.Single(i => i.ItemId == itemId).Quantity;

            //Get list of InputSheetDetailRecord
            List<InputSheetDetail> inputSheetDetailRs = this._inputSheetDetailRepository.SelectDetailsByInputSheetId(inputSheetId);
            List<InputSheetDetailRecord> result = (from x in inputSheetDetailRs
                                                    select new InputSheetDetailRecord
                                                    {
                                                        Id = x.Id,
                                                        GoodCode = x.Good.Code,
                                                        Ordered = x.InputSheet.PurchaseOrder.Orderdate,
                                                        Existing = computeExistNumber(orderedList, inputedList, x.Good.Code),
                                                        Input = x.Quantity.ToString(),
                                                        Unit = x.Good.UnitItem.Name,
                                                        Name = x.Good.Name
                                                    }).ToList<InputSheetDetailRecord>();

            return result;
        }

        /// <summary>
        /// compute number of existing
        /// </summary>
        /// <param name="orderedList"></param>
        /// <param name="inputedList"></param>
        /// <param name="goodCode"></param>
        /// <returns></returns>
        private float computeExistNumber(IList<PurchaseOrderDetail> orderedList, IList<InputedQuantityRecord> inputedList, string goodCode)
        {
            float? orderedNum = orderedList.Single(o => o.ItemId == goodCode).Quantity;
            float? inputedNum = inputedList.Single(i => i.ItemId == goodCode).Quantity;
            if (inputedNum == null || inputedNum == null)
                return 0;
            return orderedNum.Value - inputedNum.Value;
        }

        /// <summary>
        /// Save InputSheet
        /// </summary>
        /// <param name="vo"></param>
        public void SaveInputSheet(InputSheetParam vo)
        {
            if (string.IsNullOrEmpty(vo.InputSheetId))
            {
                ///
                /// =====================================
                /// Insert
                /// =====================================
                //Get new InputSheetId
                //TODO
                string maxId = this._inputRepository.SelectMaxId();
                if (string.IsNullOrEmpty(maxId))
                    return;
                int maxNum = int.Parse(maxId.Substring(3)) + 1;
                string newId = maxNum.ToString("ISD0000000000");

                //Save InputSheet
                //TODO
                InputSheet isObj = new InputSheet();
                isObj.InputSheetId = newId;
                isObj.PurchaseOrder = vo.PurchaseOrder;
                isObj.InputSheetDate = vo.InputedDate;
                isObj.DelivererName = vo.Deliver;
                isObj.ReceiverName = vo.Receiver;
                isObj.Note = vo.Description;
                isObj.InventoryNameTD = GlobalApp.Configuration.InventoryName;

                this._inputRepository.Insert(isObj);
                //Save InputSheetDetails
                //TODO
                IList<InputSheetDetailRecord> list = vo.ListInputSheetDetails;
                if (list != null && list.Count > 0)
                {
                    foreach (InputSheetDetailRecord record in list)
                    {
                        if (string.IsNullOrEmpty(record.GoodCode))
                            continue;

                        InputSheetDetail iSheetDetail = new InputSheetDetail();
                        iSheetDetail.ItemId = record.GoodCode;
                        iSheetDetail.InputSheetId = newId;
                        iSheetDetail.Quantity = float.Parse(record.Input);

                        if (checkDataInputSheetDetailValid(iSheetDetail))
                        {

                            this._inputSheetDetailRepository.Insert(iSheetDetail);
                        }
                    }
                }
            }
            else
            {                
                ///
                /// =====================================
                /// Update
                /// =====================================
                /// 
                
                //Getting and delete InputSheetDetails by InputSheetId
                List<InputSheetDetail> oldList = this._inputSheetDetailRepository.SelectDetailsByInputSheetId(vo.InputSheetId);
                foreach (InputSheetDetail item in oldList)
                {
                    if (item.Id > 0)
                    {
                        this._inputSheetDetailRepository.Delete(item.Id);
                    }
                }

                //Insert new InputSheetDetails
                List<InputSheetDetailRecord> newList = vo.ListInputSheetDetails;
                foreach (InputSheetDetailRecord record in newList)
                {
                    InputSheetDetail newISDetail = new InputSheetDetail();
                    newISDetail.ItemId = record.GoodCode;
                    newISDetail.InputSheetId = vo.InputSheetId;
                    newISDetail.Quantity = float.Parse(record.Input);
                    if (checkDataInputSheetDetailValid(newISDetail))
                    {
                        this._inputSheetDetailRepository.Insert(newISDetail);
                    } 
                }
            }
        }

        /// <summary>
        /// Check data inputsheetdetail
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private bool checkDataInputSheetDetailValid(InputSheetDetail data)
        {
            return !string.IsNullOrEmpty(data.InputSheetId) && !string.IsNullOrEmpty(data.ItemId) && data.Quantity > 0;
        }

        /// <summary>
        /// Delete an InputSheet
        /// </summary>
        /// <param name="inputSheetId"></param>
        public void DeleteInputSheet(string inputSheetId)
        {
            if (string.IsNullOrEmpty(inputSheetId))
                return;
            //Getting and delete InputSheetDetails by InputSheetId
            List<InputSheetDetail> oldList = this._inputSheetDetailRepository.SelectDetailsByInputSheetId(inputSheetId);
            foreach (InputSheetDetail item in oldList)
            {
                if (item.Id > 0)
                {
                    this._inputSheetDetailRepository.Delete(item.Id);
                }
            }
            //Delete inputSheet
            this._inputRepository.Delete(inputSheetId);
        }
    }
}